今天來實作應用程式啟動登出的流程。
首先先回顧一下時序圖:
從這張圖可以知道,今天主要要實作的是 Logout Provider。另外還有啟動登出的接口和回到應用程式的 callback 接口,總共三個。
先在 Laravel 新增相關的路由:
// Logout Provider
Route::get('/oauth2/logout', LogoutProvider::class)->name('oauth2.logout');
// 啟動 Logout 與 callback
Route::get('/logout', Logout::class)->name('logout');
Route::get('/logout/callback', LogoutCallback::class)->name('logout.callback');
再來是將這些路由註冊到 Hydra 上,首先是 Logout Provider,要在 hydra.yml
增加新的設定:
urls:
logout: http://127.0.0.1:8000/oauth2/logout
再來昨天有提到,如果要回到應用程式的某個位置的話,就需要註冊應用程式的設定,Hydra 有提供管理應用程式的 API,更新對應的指令是 update,但它實際上運作是去呼叫 Admin API 的 PUT /clients/:id
。HTTP PUT 方法會需要完整的參數內容,因此會需要把註冊的指令拿來修改一下,範例如下:
hydra --endpoint http://127.0.0.1:4445/ clients --skip-tls-verify \
update my-rp \
--grant-types authorization_code,implicit,client_credentials,refresh_token \
--response-types "code,token,id_token,token code,code id_token,id_token token,id_token token code" \
--scope openid \
--token-endpoint-auth-method client_secret_basic \
--callbacks "http://127.0.0.1:8000/callback" \
--post-logout-callbacks "http://127.0.0.1:8000/logout/callback"
最後一行 --post-logout-callbacks
是新增加的。這個設定內容所代表的是,登出後要回到哪個應用程式的哪個位置。
再來是由應用程式啟動登出,程式如下:
// 取得之前登入的 ID Token
$idToken = $request->session()->get('id_token');
// 昨天所提到的,OpenID Connect 所定義的請求欄位
$query = Arr::query([
'client_id' => 'my-rp',
'id_token_hint' => $idToken,
'post_logout_redirect_uri' => 'http://127.0.0.1:8000/logout/callback',
'state' => '1a2b3c4d',
]);
// 產生 Logout 請求
$endSessionEndpoint = 'http://127.0.0.1:4444/oauth2/sessions/logout';
$LogoutRequest = $endSessionEndpoint . '?' . $query;
return Redirect::away($LogoutRequest);
最一開始會需要之前登入成功所拿到的 ID Token。其實之前已經有埋梗:「應用程式可以先把 ID Token 保存起來,未來會使用」,登出就是其中一個使用時機。
Logout 請求的欄位,雖然 OpenID Connect 蠻多都是使用「建議」關鍵字,但實際測試過 Hydra,如果不帶任何欄位的話是可行的。但有幾個欄位會提示要提供 id_token_hint
。這點看原始碼可以了解,在沒有帶 id_token_hint
的時候,程式會認為使用者在操作「由 OP 發起的登出」,而這個情境下就不能使用 state
與 post_logout_redirect_uri
欄位。
跟 Login Provider 或 Consent Provider 一樣,會先拿到 challenge 後,取得相關資訊確認,若覺得沒問題,就由 Provider 發訊息通知 Hydra 這個操作可接受。接著就可以拿到下一關的網址,最後再使用轉導即可。
Logout Provider 的實作如下:
$logoutChallenge = $request->get('logout_challenge');
// 取得 Logout 請求物件
$logoutRequest = $adminApi->getLogoutRequest($logoutChallenge);
Log::debug('Logout Request', json_decode((string)$logoutRequest, true));
// 接受 Logout 請求
$completedRequest = $adminApi->acceptLogoutRequest($logoutChallenge);
// 轉導回 Hydra
return Redirect::away($completedRequest->getRedirectTo());
其中 LogoutRequest 裡面相關的欄位如下:
欄位 | 說明 |
---|---|
client |
應用程式相關資訊 |
request_url |
登出請求的原始 URL |
rp_initiated |
是否為 RP 發起的登出請求 |
sid |
Session ID |
subject |
使用者的唯一識別碼 |
sid
的用途之前曾賣過關子,是要用在登出的,主要是在確認使用者的狀態;而在完成登出請求的驗證後,接著就是移除 Session。
完成後,讀者實際測試的話,也許會發現跟上面講的有所出入。從瀏覽器的 Network 可以觀察到下面兩種情境:
會造成這兩個的差異的主要原因是,第一個是登入的時候有設定 remember
且時效還在的情境,第二個是登入沒有設定 remember
或時效已過。這部分可以這麼理解:因為使用 remember
代表要跟其他應用程式「共享」同一個登入狀態,因此相反的,在登出的時候可能會需要做特殊處理,因此才會導向 Logout Provider。
今天的程式可以參考 GitHub Commit,明天再接著講如何做 Backchannel Logout。